package com.ocnyang.contourview;


import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.render.*;
import ohos.agp.utils.Color;
import ohos.agp.utils.Point;
import ohos.app.Context;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/*******************************************************************
 *    * * * *   * * * *   *     *       Created by OCN.Yang
 *    *     *   *         * *   *       Time:2017/8/23 09:34.
 *    *     *   *         *   * *       Email address:ocnyang@gmail.com
 *    * * * *   * * * *   *     *.Yang  Web site:www.ocnyang.com
 *******************************************************************/


public class ContourView extends Component implements Component.EstimateSizeListener, Component.DrawTask {

    public final static String SHADER_MODE_NULL = "mode_null";
    public final static String SHADER_MODE_RADIAL = "Radial";
    public final static String SHADER_MODE_SWEEP = "Sweep";
    public final static String SHADER_MODE_LINEAR = "Linear";
    public final static String SHADER_MODE_CUSTOM = "Custom";
    public final static String SHADER_STYLE_LEFT_TO_BOTTOM = "Left_To_Bottom";
    public final static String SHADER_STYLE_RIGHT_TO_BOTTOM = "Right_To_Bottom";
    public final static String SHADER_STYLE_TOP_TO_BOTTOM = "Top_To_Bottom";
    public final static String SHADER_STYLE_CENTER = "Center";
    public final static String STYLE_SAND = "Sand";
    public final static String STYLE_CLOUDS = "Clouds";
    public final static String STYLE_RIPPLES = "Ripples";
    public final static String STYLE_BEACH = "Beach";
    public final static String STYLE_SHELL = "Shell";
    private final static String STYLE_NULL = "Style_null";
    public final static float SMOOTHNESS_DEF = 0.25F;
    private int mW;
    private int mH;
    private Color mShaderColor;
    /**
     * smoothing coefficient: 0 ~ 1 recommended range: 0.15 ~ 0.3
     */

    private float mSmoothness = 0.25F;

    private String mShaderMode = "mode_null";
    private Color mShaderStartColor;
    private Color mShaderEndColor;
    private String mShaderStyle = "Left_To_Bottom";
    private String mStyle;
    private Shader[] mShader;
    private List<Point[]> mPointsList;
    private Paint mPaint;
    private final HiLogLabel logLabel = new HiLogLabel(HiLog.LOG_APP, 0, "contourview");

    public ContourView(Context context) {
        this(context, null);
    }

    public ContourView(Context context, AttrSet attrs) {
        this(context, attrs, "");
    }

    public ContourView(Context context, AttrSet attrs, String defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mPaint = new Paint();
        mPaint.setStyle(Paint.Style.FILL_STYLE);
        mPaint.setAntiAlias(true);
        if (attrs != null) {
            if (attrs.getAttr("contour_style").isPresent()) {
                mStyle = attrs.getAttr("contour_style").get().getStringValue();
            } else {
                mStyle = STYLE_SAND;
            }

            if (attrs.getAttr("smoothness").isPresent()) {
                float smn = attrs.getAttr("smoothness").get().getFloatValue();
                if (smn <= 0) {
                    mSmoothness = 0.1F;
                } else if (smn >= 1) {
                    mSmoothness = 0.99F;
                } else {
                    mSmoothness = smn;
                }
            } else {
                mSmoothness = SMOOTHNESS_DEF;
            }

            if (attrs.getAttr("shader_mode").isPresent()) {
                mShaderMode = attrs.getAttr("shader_mode").get().getStringValue();
                if (!SHADER_MODE_NULL.equals(mShaderMode)) {
                    if (attrs.getAttr("shader_startcolor").isPresent()) {
                        mShaderStartColor = attrs.getAttr("shader_startcolor").get().getColorValue();
                    } else {
                        mShaderStartColor = new Color(Color.argb(90, 255, 255, 255));
                    }

                    if (attrs.getAttr("shader_endcolor").isPresent()) {
                        mShaderEndColor = attrs.getAttr("shader_endcolor").get().getColorValue();
                    } else {
                        mShaderEndColor = new Color(Color.argb(90, 255, 255, 255));
                    }

                    if (attrs.getAttr("shader_style").isPresent()) {
                        mShaderStyle = attrs.getAttr("shader_style").get().getStringValue();
                    } else {
                        mShaderStyle = SHADER_STYLE_LEFT_TO_BOTTOM;
                    }
                    HiLog.error(logLabel, "--" + mShaderStyle);
                } else {
                    if (attrs.getAttr("shader_color").isPresent()) {
                        mShaderColor = attrs.getAttr("shader_color").get().getColorValue();
                    } else {
                        mShaderColor = new Color(Color.argb(90, 255, 255, 255));
                    }
                }
            } else {
                mShaderMode = SHADER_MODE_NULL;
                if (attrs.getAttr("shader_color").isPresent()) {
                    mShaderColor = attrs.getAttr("shader_color").get().getColorValue();
                } else {
                    mShaderColor = new Color(Color.argb(90, 255, 255, 255));
                }
            }
        }
        // 设置测量组件的侦听器
        setEstimateSizeListener(this);
        //设置绘画监听器
        addDrawTask(this);
    }


    @Override
    public boolean onEstimateSize(int widthEstimateConfig, int heightEstimateConfig) {
        mW = Component.EstimateSpec.getSize(widthEstimateConfig);
        mH = Component.EstimateSpec.getSize(heightEstimateConfig);
        setEstimatedSize(
                EstimateSpec.getChildSizeWithMode(mW, mW, EstimateSpec.PRECISE),
                EstimateSpec.getChildSizeWithMode(mH, mH, EstimateSpec.PRECISE)
        );
        return true;
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        boolean haveShader = true;
        if (SHADER_MODE_NULL.equals(mShaderMode)) {
            mPaint.setColor(mShaderColor);
            haveShader = false;
        }
        if (!mStyle.equals(STYLE_NULL)) {
            mPointsList = PointsFactory.getPoints(mStyle, mW, mH);
        }
        int flag = 0;
        drawcontour:
        for (Point[] pts : mPointsList) {
            ++flag;
            int length = pts.length;
            if (length < 3) {
                continue drawcontour;
            }
            Path path = new Path();
            int xMin = 0, yMin = 0, xMax = 0, yMax = 0;
            for (int i = 0; i < length; i++) {
                Point pI;
                float iControl1X, iControl1Y, iControl2X, iControl2Y;
                pI = pts[i];
                if (i == 0) {
                    path.moveTo(pts[0].getPointXToInt(), pts[0].getPointYToInt());
                    xMax = xMin = pts[0].getPointXToInt();
                    yMin = yMax = pts[0].getPointYToInt();
                }
                Point[] threePoint = getThreePoint(length, i, pts);
                if (threePoint[0] == null || pI == null || threePoint[1] == null || threePoint[2] == null) {
                    continue drawcontour;
                }
                iControl1X = (float) (pI.getPointXToInt() + (((double) threePoint[1].getPointXToInt() - threePoint[0].getPointXToInt()) * mSmoothness));
                iControl1Y = (float) (pI.getPointYToInt() + (((double) threePoint[1].getPointYToInt() - threePoint[0].getPointYToInt()) * mSmoothness));
                iControl2X = (float) (threePoint[1].getPointXToInt() - (((double) threePoint[2].getPointXToInt() - pI.getPointXToInt()) * mSmoothness));
                iControl2Y = (float) ((double) threePoint[1].getPointYToInt() - (((double) threePoint[2].getPointYToInt() - pI.getPointYToInt()) * mSmoothness));
                path.cubicTo(iControl1X, iControl1Y, iControl2X, iControl2Y, threePoint[1].getPointXToInt(), threePoint[1].getPointYToInt());
                xMin = pts[i].getPointXToInt() < xMin ? pts[i].getPointXToInt() : xMin;
                xMax = pts[i].getPointXToInt() > xMax ? pts[i].getPointXToInt() : xMax;
                yMin = pts[i].getPointYToInt() < yMin ? pts[i].getPointYToInt() : yMin;
                yMax = pts[i].getPointYToInt() > yMax ? pts[i].getPointYToInt() : yMax;
            }
            if (haveShader) {
                setPaintParam(flag, xMin, xMax, yMin, yMax);
            }
            canvas.drawPath(path, mPaint);
        }
    }

    /**
     * getThreePoint
     *
     * @param length
     * @param i
     * @param pts
     * @return Point[]
     */
    private Point[] getThreePoint(int length, int i, Point[] pts) {
        Point pI$1 = null;
        Point pI_1 = null;
        Point pI_2 = null;
        if (length == 3) {
            switch (i) {
                case 0:
                    pI$1 = pts[2];
                    pI_1 = pts[1];
                    pI_2 = pts[2];
                    break;
                case 1:
                    pI$1 = pts[0];
                    pI_1 = pts[2];
                    pI_2 = pts[0];
                    break;
                case 2:
                    pI$1 = pts[1];
                    pI_1 = pts[0];
                    pI_2 = pts[1];
                    break;
                default:
                    break;
            }
        } else {
            if (i == 0) {
                pI$1 = pts[length - 1];
                pI_1 = pts[i + 1];
                pI_2 = pts[i + 2];
            } else if (i == length - 1) {
                pI$1 = pts[i - 1];
                pI_1 = pts[0];
                pI_2 = pts[1];
            } else if (i == length - 2) {
                pI$1 = pts[i - 1];
                pI_1 = pts[i + 1];
                pI_2 = pts[0];
            } else {
                pI$1 = pts[i - 1];
                pI_1 = pts[i + 1];
                pI_2 = pts[i + 2];
            }
        }
        Point[] points = new Point[]{pI$1, pI_1, pI_2};
        return points;
    }

    /**
     * setPaintParam
     *
     * @param flag
     * @param xMin
     * @param xMax
     * @param yMin
     * @param yMax
     */
    private void setPaintParam(int flag, int xMin, int xMax, int yMin, int yMax) {
        if (SHADER_MODE_CUSTOM.equals(mShaderMode) && mShader != null) {
            mPaint.setShader(mShader[(flag - 1) % mShader.length], Paint.ShaderType.LINEAR_SHADER);
        } else {
            Point startPoint, endPoint;
            switch (mShaderStyle) {
                case SHADER_STYLE_LEFT_TO_BOTTOM:
                    startPoint = new Point(xMin, yMin);
                    endPoint = new Point(xMax, yMax);
                    break;
                case SHADER_STYLE_RIGHT_TO_BOTTOM:
                    startPoint = new Point(xMax, yMin);
                    endPoint = new Point(xMin, yMax);
                    break;
                case SHADER_STYLE_TOP_TO_BOTTOM:
                    startPoint = new Point((xMax - xMin) / 2 + xMin, yMin);
                    endPoint = new Point(xMin, (yMax - yMin) / 2 + yMin);
                    break;
                case SHADER_STYLE_CENTER:
                    startPoint = new Point((xMax - xMin) / 2 + xMin, (yMax - yMin) / 2 + yMin);
                    endPoint = new Point(xMax, (yMax - yMin) / 2 + yMin);
                    break;
                default:
                    startPoint = new Point(0, 0);
                    endPoint = new Point(mW, mH);
                    break;
            }
            Color[] newColors = new Color[]{mShaderStartColor, mShaderEndColor};
            switch (mShaderMode) {
                case SHADER_MODE_RADIAL:
                    Point point = new Point(startPoint.getPointX(), endPoint.getPointY());
                    RadialShader radialShader = new RadialShader(point, (int) (Math.sqrt(Math.pow(Math.abs(xMax - xMin), 2) + Math.pow((Math.abs(yMax - yMin)), 2))),
                            null, newColors, Shader.TileMode.CLAMP_TILEMODE);
                    mPaint.setShader(radialShader, Paint.ShaderType.RADIAL_SHADER);
                    break;
                case SHADER_MODE_SWEEP:
                    SweepShader sweepShader = new SweepShader(startPoint.getPointX(), endPoint.getPointY(), newColors, null);
                    mPaint.setShader(sweepShader, Paint.ShaderType.SWEEP_SHADER);
                    break;
                case SHADER_MODE_LINEAR:
                    Point[] pointNew = new Point[]{startPoint, endPoint};
                    LinearShader linearShader = new LinearShader(pointNew, null, newColors, Shader.TileMode.REPEAT_TILEMODE);
                    mPaint.setShader(linearShader, Paint.ShaderType.LINEAR_SHADER);
                    break;
                default:
                    break;
            }
        }
    }

    /**
     * setPoints
     *
     * @param points
     */
    public void setPoints(Point... points) {
        mPointsList = new ArrayList<>(1);
        mPointsList.add(points);
        mStyle = STYLE_NULL;
    }

    /**
     * setPoints
     *
     * @param pts
     */
    public void setPoints(int... pts) {
        this.setPoints(ptsArrTopoints(pts));
    }

    /**
     * setPoints
     *
     * @param pointsArr
     */
    public void setPoints(Point[]... pointsArr) {
        mPointsList = Arrays.asList(pointsArr);
        mStyle = STYLE_NULL;
    }

    /**
     * setPoints
     *
     * @param ptsArr
     */
    public void setPoints(int[]... ptsArr) {
        mPointsList = new ArrayList<>();
        for (int[] pts : ptsArr) {
            mPointsList.add(ptsArrTopoints(pts));
        }
        mStyle = STYLE_NULL;
    }

    /**
     * ptsArrTopoints
     *
     * @param pts
     * @return Point[]
     */
    public Point[] ptsArrTopoints(int... pts) {
        Point[] points = new Point[(pts.length) / 2];
        for (int i = 0, j = 0; i < pts.length && j < points.length; i += 2, j++) {
            points[j] = new Point(pts[i], pts[i + 1]);
        }
        return points;
    }

    /**
     * getShaderMode
     *
     * @return String
     */
    public String getShaderMode() {
        return mShaderMode;
    }

    /**
     * setShaderMode
     *
     * @param shaderMode
     */
    public void setShaderMode(String shaderMode) {
        mShaderMode = shaderMode;
    }

    /**
     * getShaderStartColor
     *
     * @return Color
     */
    public Color getShaderStartColor() {
        return mShaderStartColor;
    }

    /**
     * setShaderStartColor
     *
     * @param shaderStartColor
     */
    public void setShaderStartColor(Color shaderStartColor) {
        mShaderStartColor = shaderStartColor;
    }

    /**
     * getShaderEndColor
     *
     * @return Color
     */
    public Color getShaderEndColor() {
        return mShaderEndColor;
    }

    /**
     * setShaderEndColor
     *
     * @param shaderEndColor
     */
    public void setShaderEndColor(Color shaderEndColor) {
        mShaderEndColor = shaderEndColor;
    }

    /**
     * getShaderStyle
     *
     * @return String
     */
    public String getShaderStyle() {
        return mShaderStyle;
    }

    /**
     * setShaderStyle
     *
     * @param shaderStyle
     */
    public void setShaderStyle(String shaderStyle) {
        mShaderStyle = shaderStyle;
    }

    /**
     * getStyle
     *
     * @return String
     */
    public String getStyle() {
        return mStyle;
    }

    /**
     * setStyle
     *
     * @param style
     */
    public void setStyle(String style) {
        mStyle = style;
    }

    /**
     * getShader
     *
     * @return Shader[]
     */
    public Shader[] getShader() {
        return mShader.clone();
    }

    /**
     * setShader
     *
     * @param shader
     */
    public void setShader(Shader... shader) {
        mShader = shader;
        mShaderMode = SHADER_MODE_CUSTOM;
    }

    /**
     * getSmoothness
     *
     * @return float
     */
    public float getSmoothness() {
        return mSmoothness;
    }

    /**
     * setSmoothness
     *
     * @param smoothness
     */
    public void setSmoothness(float smoothness) {
        mSmoothness = smoothness;
    }

    /**
     * getShaderColor
     *
     * @return Color
     */
    public Color getShaderColor() {
        return mShaderColor;
    }

    /**
     * setShaderColor
     *
     * @param shaderColor
     */
    public void setShaderColor(Color shaderColor) {
        mShaderColor = shaderColor;
    }

}
